卷积神经网络(Convolutional Neural Network, CNN)

项目:实现一个狗品种识别算法App

在这个notebook文件中,有些模板代码已经提供给你,但你还需要实现更多的功能来完成这个项目。除非有明确要求,你无须修改任何已给出的代码。以'(练习)'开始的标题表示接下来的代码部分中有你需要实现的功能。这些部分都配有详细的指导,需要实现的部分也会在注释中以'TODO'标出。请仔细阅读所有的提示。

除了实现代码外,你还需要回答一些与项目及代码相关的问题。每个需要回答的问题都会以 '问题 X' 标记。请仔细阅读每个问题,并且在问题后的 '回答' 部分写出完整的答案。我们将根据 你对问题的回答 和 撰写代码实现的功能 来对你提交的项目进行评分。

提示:Code 和 Markdown 区域可通过 Shift + Enter 快捷键运行。此外,Markdown可以通过双击进入编辑模式。

项目中显示为_选做_的部分可以帮助你的项目脱颖而出,而不是仅仅达到通过的最低要求。如果你决定追求更高的挑战,请在此 notebook 中完成_选做_部分的代码。


让我们开始吧

在这个notebook中,你将迈出第一步,来开发可以作为移动端或 Web应用程序一部分的算法。在这个项目的最后,你的程序将能够把用户提供的任何一个图像作为输入。如果可以从图像中检测到一只狗,它会输出对狗品种的预测。如果图像中是一个人脸,它会预测一个与其最相似的狗的种类。下面这张图展示了完成项目后可能的输出结果。(……实际上我们希望每个学生的输出结果不相同!)

Sample Dog Output

在现实世界中,你需要拼凑一系列的模型来完成不同的任务;举个例子,用来预测狗种类的算法会与预测人类的算法不同。在做项目的过程中,你可能会遇到不少失败的预测,因为并不存在完美的算法和模型。你最终提交的不完美的解决方案也一定会给你带来一个有趣的学习经验!

项目内容

我们将这个notebook分为不同的步骤,你可以使用下面的链接来浏览此notebook。

  • Step 0: 导入数据集
  • Step 1: 检测人脸
  • Step 2: 检测狗狗
  • Step 3: 从头创建一个CNN来分类狗品种
  • Step 4: 使用一个CNN来区分狗的品种(使用迁移学习)
  • Step 5: 建立一个CNN来分类狗的品种(使用迁移学习)
  • Step 6: 完成你的算法
  • Step 7: 测试你的算法

在该项目中包含了如下的问题:


步骤 0: 导入数据集

导入狗数据集

在下方的代码单元(cell)中,我们导入了一个狗图像的数据集。我们使用 scikit-learn 库中的 load_files 函数来获取一些变量:

  • train_files, valid_files, test_files - 包含图像的文件路径的numpy数组
  • train_targets, valid_targets, test_targets - 包含独热编码分类标签的numpy数组
  • dog_names - 由字符串构成的与标签相对应的狗的种类
In [1]:
from sklearn.datasets import load_files       
from keras.utils import np_utils
import numpy as np
from glob import glob

# define function to load train, test, and validation datasets
def load_dataset(path):
    data = load_files(path)
    dog_files = np.array(data['filenames'])
    dog_targets = np_utils.to_categorical(np.array(data['target']), 133)
    return dog_files, dog_targets

# load train, test, and validation datasets
train_files, train_targets = load_dataset('/data/dog_images/train')
valid_files, valid_targets = load_dataset('/data/dog_images/valid')
test_files, test_targets = load_dataset('/data/dog_images/test')

# load list of dog names
dog_names = [item[20:-1] for item in sorted(glob("/data/dog_images/train/*/"))]

# print statistics about the dataset
print('There are %d total dog categories.' % len(dog_names))
print('There are %s total dog images.\n' % len(np.hstack([train_files, valid_files, test_files])))
print('There are %d training dog images.' % len(train_files))
print('There are %d validation dog images.' % len(valid_files))
print('There are %d test dog images.'% len(test_files))
Using TensorFlow backend.
There are 133 total dog categories.
There are 8351 total dog images.

There are 6680 training dog images.
There are 835 validation dog images.
There are 836 test dog images.

导入人脸数据集

在下方的代码单元中,我们导入人脸图像数据集,文件所在路径存储在名为 human_files 的 numpy 数组。

In [2]:
import random
random.seed(8675309)

# 加载打乱后的人脸数据集的文件名
human_files = np.array(glob("/data/lfw/*/*"))
random.shuffle(human_files)

# 打印数据集的数据量
print('There are %d total human images.' % len(human_files))
There are 13233 total human images.

步骤1:检测人脸

我们将使用 OpenCV 中的 Haar feature-based cascade classifiers 来检测图像中的人脸。OpenCV 提供了很多预训练的人脸检测模型,它们以XML文件保存在 github。我们已经下载了其中一个检测模型,并且把它存储在 haarcascades 的目录中。

在如下代码单元中,我们将演示如何使用这个检测模型在样本图像中找到人脸。

In [3]:
import cv2                
import matplotlib.pyplot as plt                        
%matplotlib inline                               

# 提取预训练的人脸检测模型
face_cascade = cv2.CascadeClassifier('haarcascades/haarcascade_frontalface_alt.xml')

# 加载彩色(通道顺序为BGR)图像
img = cv2.imread(human_files[3])

# 将BGR图像进行灰度处理
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

# 在图像中找出脸
faces = face_cascade.detectMultiScale(gray)

# 打印图像中检测到的脸的个数
print('Number of faces detected:', len(faces))

# 获取每一个所检测到的脸的识别框
for (x,y,w,h) in faces:
    # 在人脸图像中绘制出识别框
    cv2.rectangle(img,(x,y),(x+w,y+h),(255,0,0),2)
    
# 将BGR图像转变为RGB图像以打印
cv_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)

# 展示含有识别框的图像
plt.imshow(cv_rgb)
plt.show()
Number of faces detected: 1

在使用任何一个检测模型之前,将图像转换为灰度图是常用过程。detectMultiScale 函数使用储存在 face_cascade 中的的数据,对输入的灰度图像进行分类。

在上方的代码中,faces 以 numpy 数组的形式,保存了识别到的面部信息。它其中每一行表示一个被检测到的脸,该数据包括如下四个信息:前两个元素 xy 代表识别框左上角的 x 和 y 坐标(参照上图,注意 y 坐标的方向和我们默认的方向不同);后两个元素代表识别框在 x 和 y 轴两个方向延伸的长度 wd

写一个人脸识别器

我们可以将这个程序封装为一个函数。该函数的输入为人脸图像的路径,当图像中包含人脸时,该函数返回 True,反之返回 False。该函数定义如下所示。

In [4]:
# 如果img_path路径表示的图像检测到了脸,返回"True" 
def face_detector(img_path):
    img = cv2.imread(img_path)
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    faces = face_cascade.detectMultiScale(gray)
    return len(faces) > 0

【练习】 评估人脸检测模型


问题 1:

在下方的代码块中,使用 face_detector 函数,计算:

  • human_files 的前100张图像中,能够检测到人脸的图像占比多少?
  • dog_files 的前100张图像中,能够检测到人脸的图像占比多少?

理想情况下,人图像中检测到人脸的概率应当为100%,而狗图像中检测到人脸的概率应该为0%。你会发现我们的算法并非完美,但结果仍然是可以接受的。我们从每个数据集中提取前100个图像的文件路径,并将它们存储在human_files_shortdog_files_short中。

In [5]:
human_files_short = human_files[:100]
dog_files_short = train_files[:100]
## 请不要修改上方代码

## TODO: 基于human_files_short和dog_files_short
## 中的图像测试face_detector的表现

def detect(detector, file):
    return np.mean([detector(i) for i in file])
print('human: {:.2%}'.format(detect(face_detector, human_files_short)))
print('dog: {:.2%}'.format(detect(face_detector, dog_files_short)))
human: 100.00%
dog: 11.00%

回答1:

  • human_files 的前100张图像中,能够检测到人脸的图像占100%
  • dog_files 的前100张图像中,能够检测到人脸的图像占11%

问题 2:

就算法而言,该算法成功与否的关键在于,用户能否提供含有清晰面部特征的人脸图像。 那么你认为,这样的要求在实际使用中对用户合理吗?如果你觉得不合理,你能否想到一个方法,即使图像中并没有清晰的面部特征,也能够检测到人脸?

回答2:

我认为这样的要求在实际中对用户不合理。 我认为除了面部特征之外,应该加上人脸外型轮廓、肤色面积、头发等等,作为其中的特征。 除此之外,应该在训练的时候加入各种的侧脸的图片,增强对于面部特征不过清晰情况下的识别。


选做:

我们建议在你的算法中使用opencv的人脸检测模型去检测人类图像,不过你可以自由地探索其他的方法,尤其是尝试使用深度学习来解决它:)。请用下方的代码单元来设计和测试你的面部监测算法。如果你决定完成这个_选做_任务,你需要报告算法在每一个数据集上的表现。

In [6]:
## (选做) TODO: 报告另一个面部检测算法在LFW数据集上的表现
### 你可以随意使用所需的代码单元数

步骤 2: 检测狗狗

在这个部分中,我们使用预训练的 ResNet-50 模型去检测图像中的狗。下方的第一行代码就是下载了 ResNet-50 模型的网络结构参数,以及基于 ImageNet 数据集的预训练权重。

ImageNet 这目前一个非常流行的数据集,常被用来测试图像分类等计算机视觉任务相关的算法。它包含超过一千万个 URL,每一个都链接到 1000 categories 中所对应的一个物体的图像。任给输入一个图像,该 ResNet-50 模型会返回一个对图像中物体的预测结果。

In [7]:
from keras.applications.resnet50 import ResNet50

# 定义ResNet50模型
ResNet50_model = ResNet50(weights='imagenet')
Downloading data from https://github.com/fchollet/deep-learning-models/releases/download/v0.2/resnet50_weights_tf_dim_ordering_tf_kernels.h5
102858752/102853048 [==============================] - 2s 0us/step

数据预处理

  • 在使用 TensorFlow 作为后端的时候,在 Keras 中,CNN 的输入是一个4维数组(也被称作4维张量),它的各维度尺寸为 (nb_samples, rows, columns, channels)。其中 nb_samples 表示图像(或者样本)的总数,rows, columns, 和 channels 分别表示图像的行数、列数和通道数。
  • 下方的 path_to_tensor 函数实现如下将彩色图像的字符串型的文件路径作为输入,返回一个4维张量,作为 Keras CNN 输入。因为我们的输入图像是彩色图像,因此它们具有三个通道( channels3)。
    1. 该函数首先读取一张图像,然后将其缩放为 224×224 的图像。
    2. 随后,该图像被调整为具有4个维度的张量。
    3. 对于任一输入图像,最后返回的张量的维度是:(1, 224, 224, 3)
  • paths_to_tensor 函数将图像路径的字符串组成的 numpy 数组作为输入,并返回一个4维张量,各维度尺寸为 (nb_samples, 224, 224, 3)。 在这里,nb_samples是提供的图像路径的数据中的样本数量或图像数量。你也可以将 nb_samples 理解为数据集中3维张量的个数(每个3维张量表示一个不同的图像。
In [8]:
from keras.preprocessing import image                  
from tqdm import tqdm

def path_to_tensor(img_path):
    # 用PIL加载RGB图像为PIL.Image.Image类型
    img = image.load_img(img_path, target_size=(224, 224))
    # 将PIL.Image.Image类型转化为格式为(224, 224, 3)的3维张量
    x = image.img_to_array(img)
    # 将3维张量转化为格式为(1, 224, 224, 3)的4维张量并返回
    return np.expand_dims(x, axis=0)

def paths_to_tensor(img_paths):
    list_of_tensors = [path_to_tensor(img_path) for img_path in tqdm(img_paths)]
    return np.vstack(list_of_tensors)

基于 ResNet-50 架构进行预测

对于通过上述步骤得到的四维张量,在把它们输入到 ResNet-50 网络、或 Keras 中其他类似的预训练模型之前,还需要进行一些额外的处理:

  1. 首先,这些图像的通道顺序为 RGB,我们需要重排他们的通道顺序为 BGR。
  2. 其次,预训练模型的输入都进行了额外的归一化过程。因此我们在这里也要对这些张量进行归一化,即对所有图像所有像素都减去像素均值 [103.939, 116.779, 123.68](以 RGB 模式表示,根据所有的 ImageNet 图像算出)。

导入的 preprocess_input 函数实现了这些功能。如果你对此很感兴趣,可以在 这里 查看 preprocess_input的代码。

在实现了图像处理的部分之后,我们就可以使用模型来进行预测。这一步通过 predict 方法来实现,它返回一个向量,向量的第 i 个元素表示该图像属于第 i 个 ImageNet 类别的概率。这通过如下的 ResNet50_predict_labels 函数实现。

通过对预测出的向量取用 argmax 函数(找到有最大概率值的下标序号),我们可以得到一个整数,即模型预测到的物体的类别。进而根据这个 清单,我们能够知道这具体是哪个品种的狗狗。

In [9]:
from keras.applications.resnet50 import preprocess_input, decode_predictions
def ResNet50_predict_labels(img_path):
    # 返回img_path路径的图像的预测向量
    img = preprocess_input(path_to_tensor(img_path))
    return np.argmax(ResNet50_model.predict(img))

完成狗检测模型

在研究该 清单 的时候,你会注意到,狗类别对应的序号为151-268。因此,在检查预训练模型判断图像是否包含狗的时候,我们只需要检查如上的 ResNet50_predict_labels 函数是否返回一个介于151和268之间(包含区间端点)的值。

我们通过这些想法来完成下方的 dog_detector 函数,如果从图像中检测到狗就返回 True,否则返回 False

In [10]:
def dog_detector(img_path):
    prediction = ResNet50_predict_labels(img_path)
    return ((prediction <= 268) & (prediction >= 151)) 

【作业】评估狗狗检测模型


问题 3:

在下方的代码块中,使用 dog_detector 函数,计算:

  • human_files_short中图像检测到狗狗的百分比?
  • dog_files_short中图像检测到狗狗的百分比?
In [11]:
### TODO: 测试dog_detector函数在human_files_short和dog_files_short的表现

# dog detection for human files
print('human: {:.2%}'.format(detect(dog_detector, human_files_short)))
print('dog: {:.2%}'.format(detect(dog_detector, dog_files_short)))
human: 0.00%
dog: 100.00%

回答3:

  • human_files_short中图像检测到狗狗的百分比为0%
  • dog_files_short中图像检测到狗狗的百分比为100%

步骤 3: 从头开始创建一个CNN来分类狗品种

现在我们已经实现了一个函数,能够在图像中识别人类及狗狗。但我们需要更进一步的方法,来对狗的类别进行识别。在这一步中,你需要实现一个卷积神经网络来对狗的品种进行分类。你需要从头实现你的卷积神经网络(在这一阶段,你还不能使用迁移学习),并且你需要达到超过1%的测试集准确率。在本项目的步骤五中,你还有机会使用迁移学习来实现一个准确率大大提高的模型。

在添加卷积层的时候,注意不要加上太多的(可训练的)层。更多的参数意味着更长的训练时间,也就是说你更可能需要一个 GPU 来加速训练过程。万幸的是,Keras 提供了能够轻松预测每次迭代(epoch)花费时间所需的函数。你可以据此推断你算法所需的训练时间。

值得注意的是,对狗的图像进行分类是一项极具挑战性的任务。因为即便是一个正常人,也很难区分布列塔尼犬和威尔士史宾格犬。

布列塔尼犬(Brittany) 威尔士史宾格犬(Welsh Springer Spaniel)

不难发现其他的狗品种会有很小的类间差别(比如金毛寻回犬和美国水猎犬)。

金毛寻回犬(Curly-Coated Retriever) 美国水猎犬(American Water Spaniel)

同样,拉布拉多犬(labradors)有黄色、棕色和黑色这三种。那么你设计的基于视觉的算法将不得不克服这种较高的类间差别,以达到能够将这些不同颜色的同类狗分到同一个品种中。

黄色拉布拉多犬(Yellow Labrador) 棕色拉布拉多犬(Chocolate Labrador) 黑色拉布拉多犬(Black Labrador)

我们也提到了随机分类将得到一个非常低的结果:不考虑品种略有失衡的影响,随机猜测到正确品种的概率是1/133,相对应的准确率是低于1%的。

请记住,在深度学习领域,实践远远高于理论。大量尝试不同的框架吧,相信你的直觉!当然,玩得开心!

数据预处理

通过对每张图像的像素值除以255,我们对图像实现了归一化处理。

In [12]:
from PIL import ImageFile                            
ImageFile.LOAD_TRUNCATED_IMAGES = True                 

# Keras中的数据预处理过程
train_tensors = paths_to_tensor(train_files).astype('float32')/255
valid_tensors = paths_to_tensor(valid_files).astype('float32')/255
test_tensors = paths_to_tensor(test_files).astype('float32')/255
100%|██████████| 6680/6680 [01:27<00:00, 48.25it/s] 
100%|██████████| 835/835 [00:09<00:00, 83.92it/s] 
100%|██████████| 836/836 [00:09<00:00, 85.05it/s] 

【练习】模型架构

创建一个卷积神经网络来对狗品种进行分类。在你代码块的最后,执行 model.summary() 来输出你模型的总结信息。

我们已经帮你导入了一些所需的 Python 库,如有需要你可以自行导入。如果你在过程中遇到了困难,如下是给你的一点小提示——该模型能够在5个 epoch 内取得超过1%的测试准确率,并且能在CPU上很快地训练。

Sample CNN


问题 4:

在下方的代码块中尝试使用 Keras 搭建卷积网络的架构,并回答相关的问题。

  1. 你可以尝试自己搭建一个卷积网络的模型,那么你需要回答你搭建卷积网络的具体步骤(用了哪些层)以及为什么这样搭建。
  2. 你也可以根据上图提示的步骤搭建卷积网络,那么请说明为何如上的架构能够在该问题上取得很好的表现。

回答4:

上图提示的架构为标准的深度学习网络架构,由卷积层与池化层相间而成,最后为Dense层。
其中卷积层提取特征,池化层避免过拟合。
Max-pooling强化每个特征,Global Average-Pooling则保留图片的纹理信息。

In [13]:
from keras.layers import Conv2D, MaxPooling2D, GlobalAveragePooling2D
from keras.layers import Dropout, Flatten, Dense
from keras.models import Sequential

model = Sequential()

### TODO: 定义你的网络架构
model.add(Conv2D(filters=16, kernel_size=2, padding='valid', activation='relu',
                input_shape=(224,224,3)))
model.add(MaxPooling2D(pool_size=2))
model.add(Conv2D(filters=32, kernel_size=2, padding='valid', activation='relu'))
model.add(MaxPooling2D(pool_size=2))
model.add(Conv2D(filters=64, kernel_size=2, padding='same', activation='relu'))
model.add(MaxPooling2D(pool_size=2))
model.add(GlobalAveragePooling2D())
model.add(Dense(133, activation='softmax'))

model.summary()
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
conv2d_1 (Conv2D)            (None, 223, 223, 16)      208       
_________________________________________________________________
max_pooling2d_2 (MaxPooling2 (None, 111, 111, 16)      0         
_________________________________________________________________
conv2d_2 (Conv2D)            (None, 110, 110, 32)      2080      
_________________________________________________________________
max_pooling2d_3 (MaxPooling2 (None, 55, 55, 32)        0         
_________________________________________________________________
conv2d_3 (Conv2D)            (None, 55, 55, 64)        8256      
_________________________________________________________________
max_pooling2d_4 (MaxPooling2 (None, 27, 27, 64)        0         
_________________________________________________________________
global_average_pooling2d_1 ( (None, 64)                0         
_________________________________________________________________
dense_1 (Dense)              (None, 133)               8645      
=================================================================
Total params: 19,189
Trainable params: 19,189
Non-trainable params: 0
_________________________________________________________________
In [14]:
## 编译模型
model.compile(optimizer='rmsprop', loss='categorical_crossentropy', metrics=['accuracy'])

【练习】训练模型


问题 5:

在下方代码单元训练模型。使用模型检查点(model checkpointing)来储存具有最低验证集 loss 的模型。

可选题:你也可以对训练集进行 数据增强,来优化模型的表现。

In [25]:
from keras.callbacks import ModelCheckpoint  

### TODO: 设置训练模型的epochs的数量

epochs = 5


### 不要修改下方代码

checkpointer = ModelCheckpoint(filepath='saved_models/weights.best.from_scratch.hdf5', 
                               verbose=1, save_best_only=True)

model.fit(train_tensors, train_targets, 
          validation_data=(valid_tensors, valid_targets),
          epochs=epochs, batch_size=20, callbacks=[checkpointer], verbose=1)
Train on 6680 samples, validate on 835 samples
Epoch 1/5
6660/6680 [============================>.] - ETA: 0s - loss: 4.7292 - acc: 0.0245Epoch 00001: val_loss improved from inf to 4.75188, saving model to saved_models/weights.best.from_scratch.hdf5
6680/6680 [==============================] - 21s 3ms/step - loss: 4.7296 - acc: 0.0246 - val_loss: 4.7519 - val_acc: 0.0251
Epoch 2/5
6660/6680 [============================>.] - ETA: 0s - loss: 4.7088 - acc: 0.0254Epoch 00002: val_loss improved from 4.75188 to 4.73937, saving model to saved_models/weights.best.from_scratch.hdf5
6680/6680 [==============================] - 21s 3ms/step - loss: 4.7089 - acc: 0.0257 - val_loss: 4.7394 - val_acc: 0.0275
Epoch 3/5
6660/6680 [============================>.] - ETA: 0s - loss: 4.6913 - acc: 0.0303Epoch 00003: val_loss improved from 4.73937 to 4.71945, saving model to saved_models/weights.best.from_scratch.hdf5
6680/6680 [==============================] - 21s 3ms/step - loss: 4.6917 - acc: 0.0304 - val_loss: 4.7195 - val_acc: 0.0299
Epoch 4/5
6660/6680 [============================>.] - ETA: 0s - loss: 4.6729 - acc: 0.0305Epoch 00004: val_loss improved from 4.71945 to 4.71236, saving model to saved_models/weights.best.from_scratch.hdf5
6680/6680 [==============================] - 21s 3ms/step - loss: 4.6727 - acc: 0.0307 - val_loss: 4.7124 - val_acc: 0.0287
Epoch 5/5
6660/6680 [============================>.] - ETA: 0s - loss: 4.6576 - acc: 0.0312Epoch 00005: val_loss improved from 4.71236 to 4.69711, saving model to saved_models/weights.best.from_scratch.hdf5
6680/6680 [==============================] - 21s 3ms/step - loss: 4.6583 - acc: 0.0313 - val_loss: 4.6971 - val_acc: 0.0204
Out[25]:
<keras.callbacks.History at 0x7f133e3dcfd0>
In [26]:
## 加载具有最好验证loss的模型

model.load_weights('saved_models/weights.best.from_scratch.hdf5')

测试模型

在狗图像的测试数据集上试用你的模型。确保测试准确率大于1%。

In [27]:
# 获取测试数据集中每一个图像所预测的狗品种的index
dog_breed_predictions = [np.argmax(model.predict(np.expand_dims(tensor, axis=0))) for tensor in test_tensors]

# 报告测试准确率
test_accuracy = 100*np.sum(np.array(dog_breed_predictions)==np.argmax(test_targets, axis=1))/len(dog_breed_predictions)
print('Test accuracy: %.4f%%' % test_accuracy)
Test accuracy: 3.3493%

步骤 4: 使用一个CNN来区分狗的品种

使用 迁移学习(Transfer Learning)的方法,能帮助我们在不损失准确率的情况下大大减少训练时间。在以下步骤中,你可以尝试使用迁移学习来训练你自己的CNN。

得到从图像中提取的特征向量(Bottleneck Features)

In [28]:
bottleneck_features = np.load('/data/bottleneck_features/DogVGG16Data.npz')
train_VGG16 = bottleneck_features['train']
valid_VGG16 = bottleneck_features['valid']
test_VGG16 = bottleneck_features['test']

模型架构

该模型使用预训练的 VGG-16 模型作为固定的图像特征提取器,其中 VGG-16 最后一层卷积层的输出被直接输入到我们的模型。我们只需要添加一个全局平均池化层以及一个全连接层,其中全连接层使用 softmax 激活函数,对每一个狗的种类都包含一个节点。

In [29]:
VGG16_model = Sequential()
VGG16_model.add(GlobalAveragePooling2D(input_shape=train_VGG16.shape[1:]))
VGG16_model.add(Dense(133, activation='softmax'))

VGG16_model.summary()
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
global_average_pooling2d_3 ( (None, 512)               0         
_________________________________________________________________
dense_3 (Dense)              (None, 133)               68229     
=================================================================
Total params: 68,229
Trainable params: 68,229
Non-trainable params: 0
_________________________________________________________________
In [30]:
## 编译模型

VGG16_model.compile(loss='categorical_crossentropy', optimizer='rmsprop', metrics=['accuracy'])
In [31]:
## 训练模型

checkpointer = ModelCheckpoint(filepath='saved_models/weights.best.VGG16.hdf5', 
                               verbose=1, save_best_only=True)

VGG16_model.fit(train_VGG16, train_targets, 
          validation_data=(valid_VGG16, valid_targets),
          epochs=20, batch_size=20, callbacks=[checkpointer], verbose=1)
Train on 6680 samples, validate on 835 samples
Epoch 1/20
6580/6680 [============================>.] - ETA: 0s - loss: 12.6386 - acc: 0.1155Epoch 00001: val_loss improved from inf to 11.30424, saving model to saved_models/weights.best.VGG16.hdf5
6680/6680 [==============================] - 2s 319us/step - loss: 12.6263 - acc: 0.1159 - val_loss: 11.3042 - val_acc: 0.1904
Epoch 2/20
6580/6680 [============================>.] - ETA: 0s - loss: 10.7051 - acc: 0.2616Epoch 00002: val_loss improved from 11.30424 to 10.76961, saving model to saved_models/weights.best.VGG16.hdf5
6680/6680 [==============================] - 2s 261us/step - loss: 10.7012 - acc: 0.2617 - val_loss: 10.7696 - val_acc: 0.2455
Epoch 3/20
6620/6680 [============================>.] - ETA: 0s - loss: 10.2359 - acc: 0.3112Epoch 00003: val_loss improved from 10.76961 to 10.37310, saving model to saved_models/weights.best.VGG16.hdf5
6680/6680 [==============================] - 2s 260us/step - loss: 10.2241 - acc: 0.3120 - val_loss: 10.3731 - val_acc: 0.2802
Epoch 4/20
6620/6680 [============================>.] - ETA: 0s - loss: 9.9408 - acc: 0.3443Epoch 00004: val_loss improved from 10.37310 to 10.20374, saving model to saved_models/weights.best.VGG16.hdf5
6680/6680 [==============================] - 2s 260us/step - loss: 9.9351 - acc: 0.3448 - val_loss: 10.2037 - val_acc: 0.2886
Epoch 5/20
6620/6680 [============================>.] - ETA: 0s - loss: 9.7609 - acc: 0.3647Epoch 00005: val_loss improved from 10.20374 to 10.15263, saving model to saved_models/weights.best.VGG16.hdf5
6680/6680 [==============================] - 2s 260us/step - loss: 9.7704 - acc: 0.3642 - val_loss: 10.1526 - val_acc: 0.3030
Epoch 6/20
6580/6680 [============================>.] - ETA: 0s - loss: 9.6956 - acc: 0.3736Epoch 00006: val_loss improved from 10.15263 to 10.09281, saving model to saved_models/weights.best.VGG16.hdf5
6680/6680 [==============================] - 2s 263us/step - loss: 9.6929 - acc: 0.3741 - val_loss: 10.0928 - val_acc: 0.3066
Epoch 7/20
6620/6680 [============================>.] - ETA: 0s - loss: 9.6283 - acc: 0.3852Epoch 00007: val_loss did not improve
6680/6680 [==============================] - 2s 260us/step - loss: 9.6325 - acc: 0.3850 - val_loss: 10.1105 - val_acc: 0.3150
Epoch 8/20
6520/6680 [============================>.] - ETA: 0s - loss: 9.5363 - acc: 0.3916Epoch 00008: val_loss improved from 10.09281 to 9.96390, saving model to saved_models/weights.best.VGG16.hdf5
6680/6680 [==============================] - 2s 264us/step - loss: 9.5387 - acc: 0.3912 - val_loss: 9.9639 - val_acc: 0.3174
Epoch 9/20
6620/6680 [============================>.] - ETA: 0s - loss: 9.3105 - acc: 0.4063Epoch 00009: val_loss improved from 9.96390 to 9.76292, saving model to saved_models/weights.best.VGG16.hdf5
6680/6680 [==============================] - 2s 261us/step - loss: 9.3066 - acc: 0.4067 - val_loss: 9.7629 - val_acc: 0.3281
Epoch 10/20
6620/6680 [============================>.] - ETA: 0s - loss: 9.2584 - acc: 0.4163Epoch 00010: val_loss improved from 9.76292 to 9.72755, saving model to saved_models/weights.best.VGG16.hdf5
6680/6680 [==============================] - 2s 261us/step - loss: 9.2549 - acc: 0.4166 - val_loss: 9.7275 - val_acc: 0.3425
Epoch 11/20
6620/6680 [============================>.] - ETA: 0s - loss: 9.1646 - acc: 0.4201Epoch 00011: val_loss improved from 9.72755 to 9.58206, saving model to saved_models/weights.best.VGG16.hdf5
6680/6680 [==============================] - 2s 260us/step - loss: 9.1720 - acc: 0.4196 - val_loss: 9.5821 - val_acc: 0.3389
Epoch 12/20
6620/6680 [============================>.] - ETA: 0s - loss: 8.9689 - acc: 0.4264Epoch 00012: val_loss improved from 9.58206 to 9.41290, saving model to saved_models/weights.best.VGG16.hdf5
6680/6680 [==============================] - 2s 261us/step - loss: 8.9680 - acc: 0.4266 - val_loss: 9.4129 - val_acc: 0.3593
Epoch 13/20
6600/6680 [============================>.] - ETA: 0s - loss: 8.7981 - acc: 0.4395Epoch 00013: val_loss improved from 9.41290 to 9.30721, saving model to saved_models/weights.best.VGG16.hdf5
6680/6680 [==============================] - 2s 262us/step - loss: 8.8292 - acc: 0.4376 - val_loss: 9.3072 - val_acc: 0.3629
Epoch 14/20
6600/6680 [============================>.] - ETA: 0s - loss: 8.7789 - acc: 0.4455Epoch 00014: val_loss did not improve
6680/6680 [==============================] - 2s 263us/step - loss: 8.7887 - acc: 0.4449 - val_loss: 9.3362 - val_acc: 0.3569
Epoch 15/20
6600/6680 [============================>.] - ETA: 0s - loss: 8.7047 - acc: 0.4498Epoch 00015: val_loss improved from 9.30721 to 9.24949, saving model to saved_models/weights.best.VGG16.hdf5
6680/6680 [==============================] - 2s 262us/step - loss: 8.6996 - acc: 0.4501 - val_loss: 9.2495 - val_acc: 0.3641
Epoch 16/20
6560/6680 [============================>.] - ETA: 0s - loss: 8.6378 - acc: 0.4561Epoch 00016: val_loss improved from 9.24949 to 9.20499, saving model to saved_models/weights.best.VGG16.hdf5
6680/6680 [==============================] - 2s 264us/step - loss: 8.6383 - acc: 0.4560 - val_loss: 9.2050 - val_acc: 0.3725
Epoch 17/20
6560/6680 [============================>.] - ETA: 0s - loss: 8.5109 - acc: 0.4637Epoch 00017: val_loss improved from 9.20499 to 9.06928, saving model to saved_models/weights.best.VGG16.hdf5
6680/6680 [==============================] - 2s 263us/step - loss: 8.5296 - acc: 0.4626 - val_loss: 9.0693 - val_acc: 0.3701
Epoch 18/20
6600/6680 [============================>.] - ETA: 0s - loss: 8.3844 - acc: 0.4671Epoch 00018: val_loss improved from 9.06928 to 8.92825, saving model to saved_models/weights.best.VGG16.hdf5
6680/6680 [==============================] - 2s 263us/step - loss: 8.3880 - acc: 0.4669 - val_loss: 8.9282 - val_acc: 0.3832
Epoch 19/20
6560/6680 [============================>.] - ETA: 0s - loss: 8.2757 - acc: 0.4768Epoch 00019: val_loss improved from 8.92825 to 8.78872, saving model to saved_models/weights.best.VGG16.hdf5
6680/6680 [==============================] - 2s 263us/step - loss: 8.2751 - acc: 0.4769 - val_loss: 8.7887 - val_acc: 0.4036
Epoch 20/20
6600/6680 [============================>.] - ETA: 0s - loss: 8.2539 - acc: 0.4808Epoch 00020: val_loss did not improve
6680/6680 [==============================] - 2s 261us/step - loss: 8.2473 - acc: 0.4811 - val_loss: 8.9351 - val_acc: 0.3760
Out[31]:
<keras.callbacks.History at 0x7f133e436588>
In [32]:
## 加载具有最好验证loss的模型

VGG16_model.load_weights('saved_models/weights.best.VGG16.hdf5')

测试模型

现在,我们可以测试此CNN在狗图像测试数据集中识别品种的效果如何。我们在下方打印出测试准确率。

In [33]:
# 获取测试数据集中每一个图像所预测的狗品种的index
VGG16_predictions = [np.argmax(VGG16_model.predict(np.expand_dims(feature, axis=0))) for feature in test_VGG16]

# 报告测试准确率
test_accuracy = 100*np.sum(np.array(VGG16_predictions)==np.argmax(test_targets, axis=1))/len(VGG16_predictions)
print('Test accuracy: %.4f%%' % test_accuracy)
Test accuracy: 37.7990%

使用模型预测狗的品种

In [24]:
from extract_bottleneck_features import *

def VGG16_predict_breed(img_path):
    # 提取bottleneck特征
    bottleneck_feature = extract_VGG16(path_to_tensor(img_path))
    # 获取预测向量
    predicted_vector = VGG16_model.predict(bottleneck_feature)
    # 返回此模型预测的狗的品种
    return dog_names[np.argmax(predicted_vector)]

步骤 5: 建立一个CNN来分类狗的品种(使用迁移学习)

现在你将使用迁移学习来建立一个CNN,从而可以从图像中识别狗的品种。你的 CNN 在测试集上的准确率必须至少达到60%。

在步骤4中,我们使用了迁移学习来创建一个使用基于 VGG-16 提取的特征向量来搭建一个 CNN。在本部分内容中,你必须使用另一个预训练模型来搭建一个 CNN。为了让这个任务更易实现,我们已经预先对目前 keras 中可用的几种网络进行了预训练:

这些文件被命名为为:

Dog{network}Data.npz

其中 {network} 可以是 VGG19Resnet50InceptionV3Xception 中的一个。选择上方网络架构中的一个,他们已经保存在目录 /data/bottleneck_features/ 中。

【练习】获取模型的特征向量

在下方代码块中,通过运行下方代码提取训练、测试与验证集相对应的bottleneck特征。

bottleneck_features = np.load('/data/bottleneck_features/Dog{network}Data.npz')
train_{network} = bottleneck_features['train']
valid_{network} = bottleneck_features['valid']
test_{network} = bottleneck_features['test']
In [34]:
### TODO: 从另一个预训练的CNN获取bottleneck特征
bottleneck_features = np.load('/data/bottleneck_features/DogResnet50Data.npz')
train_Resnet50 = bottleneck_features['train']
valid_Resnet50 = bottleneck_features['valid']
test_Resnet50 = bottleneck_features['test']

【练习】模型架构

建立一个CNN来分类狗品种。在你的代码单元块的最后,通过运行如下代码输出网络的结构:

    <your model's name>.summary()


问题 6:

在下方的代码块中尝试使用 Keras 搭建最终的网络架构,并回答你实现最终 CNN 架构的步骤与每一步的作用,并描述你在迁移学习过程中,使用该网络架构的原因。

回答6:

  • 模型架构: 在Resnet50架构之后,加上了一个全局平均池化层,加深模型对于纹理的理解。最后以Dense层,softmax函数作为输出。

  • 为什么相比普通的CNN,迁移学习可以取得更好的效果? 迁移学习站在巨人的肩膀上,使用众多科学家努力完成、调优的模型,处理图片识别任务中,前期对于边缘、色块、线条等的识别,仅在最后加上与训练数据集相关的层级,可以快速搭建一个效果好的模型。

相比普通CNN,未把最后的与训练集有关的层级去除,因此效果没有迁移学习好。

  • 为什么第三步中的尝试没有迁移学习的效果好? 承上,第三步的尝试中,模型未经过调优,对于前期边缘、色块、线条等的识别、后期较特定的过滤器,表现均不好,因此效果没有迁移学习好。

  • 为什么选择了Resnet50,而不是其他模型? 选择Resnet50的原因,Resnet以他非常深的层级著称,最高152层。由于具有很深的层级,Resnet加入了一些链接,避免训练的过程中,产生信号梯度消失的问题。我认为这样的设置非常聪明,通过加入一些跨层的链接,协助信号的传递,因此尝试使用了Resnet50搭建迁移学习的模型。

参考资料:ResNet的论文

In [35]:
### TODO: 定义你的框架
Resnet50_model = Sequential()
Resnet50_model.add(GlobalAveragePooling2D(input_shape=(1,1,2048)))
Resnet50_model.add(Dense(133, activation = 'softmax'))
Resnet50_model.summary()
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
global_average_pooling2d_4 ( (None, 2048)              0         
_________________________________________________________________
dense_4 (Dense)              (None, 133)               272517    
=================================================================
Total params: 272,517
Trainable params: 272,517
Non-trainable params: 0
_________________________________________________________________
In [36]:
### TODO: 编译模型
Resnet50_model.compile(loss='categorical_crossentropy', optimizer='rmsprop',
              metrics=['accuracy'])

【练习】训练模型

问题 7:

在下方代码单元中训练你的模型。使用模型检查点(model checkpointing)来储存具有最低验证集 loss 的模型。

当然,你也可以对训练集进行 数据增强 以优化模型的表现,不过这不是必须的步骤。

In [37]:
### TODO: 训练模型
checkpointer = ModelCheckpoint(filepath='DogResnet50.weights.best.hdf5', verbose=1,
                              save_best_only=True)
Resnet50_model.fit(train_Resnet50, train_targets, epochs=20, validation_data=(valid_Resnet50, valid_targets),
         callbacks=[checkpointer], verbose=1, shuffle=True)
Train on 6680 samples, validate on 835 samples
Epoch 1/20
6400/6680 [===========================>..] - ETA: 0s - loss: 1.8049 - acc: 0.5717Epoch 00001: val_loss improved from inf to 0.88655, saving model to DogResnet50.weights.best.hdf5
6680/6680 [==============================] - 1s 215us/step - loss: 1.7705 - acc: 0.5772 - val_loss: 0.8865 - val_acc: 0.7186
Epoch 2/20
6368/6680 [===========================>..] - ETA: 0s - loss: 0.4563 - acc: 0.8617Epoch 00002: val_loss improved from 0.88655 to 0.66640, saving model to DogResnet50.weights.best.hdf5
6680/6680 [==============================] - 1s 162us/step - loss: 0.4549 - acc: 0.8614 - val_loss: 0.6664 - val_acc: 0.8012
Epoch 3/20
6592/6680 [============================>.] - ETA: 0s - loss: 0.2612 - acc: 0.9229Epoch 00003: val_loss improved from 0.66640 to 0.63755, saving model to DogResnet50.weights.best.hdf5
6680/6680 [==============================] - 1s 156us/step - loss: 0.2611 - acc: 0.9231 - val_loss: 0.6375 - val_acc: 0.8120
Epoch 4/20
6528/6680 [============================>.] - ETA: 0s - loss: 0.1645 - acc: 0.9528Epoch 00004: val_loss improved from 0.63755 to 0.63468, saving model to DogResnet50.weights.best.hdf5
6680/6680 [==============================] - 1s 156us/step - loss: 0.1655 - acc: 0.9525 - val_loss: 0.6347 - val_acc: 0.8132
Epoch 5/20
6464/6680 [============================>.] - ETA: 0s - loss: 0.1099 - acc: 0.9697Epoch 00005: val_loss improved from 0.63468 to 0.63287, saving model to DogResnet50.weights.best.hdf5
6680/6680 [==============================] - 1s 158us/step - loss: 0.1103 - acc: 0.9696 - val_loss: 0.6329 - val_acc: 0.8216
Epoch 6/20
6336/6680 [===========================>..] - ETA: 0s - loss: 0.0720 - acc: 0.9814Epoch 00006: val_loss did not improve
6680/6680 [==============================] - 1s 153us/step - loss: 0.0751 - acc: 0.9796 - val_loss: 0.6858 - val_acc: 0.8216
Epoch 7/20
6592/6680 [============================>.] - ETA: 0s - loss: 0.0545 - acc: 0.9853Epoch 00007: val_loss did not improve
6680/6680 [==============================] - 1s 154us/step - loss: 0.0548 - acc: 0.9849 - val_loss: 0.6678 - val_acc: 0.8192
Epoch 8/20
6592/6680 [============================>.] - ETA: 0s - loss: 0.0386 - acc: 0.9921Epoch 00008: val_loss did not improve
6680/6680 [==============================] - 1s 154us/step - loss: 0.0385 - acc: 0.9921 - val_loss: 0.6657 - val_acc: 0.8156
Epoch 9/20
6624/6680 [============================>.] - ETA: 0s - loss: 0.0291 - acc: 0.9940Epoch 00009: val_loss did not improve
6680/6680 [==============================] - 1s 154us/step - loss: 0.0289 - acc: 0.9940 - val_loss: 0.6991 - val_acc: 0.8216
Epoch 10/20
6528/6680 [============================>.] - ETA: 0s - loss: 0.0209 - acc: 0.9960Epoch 00010: val_loss did not improve
6680/6680 [==============================] - 1s 154us/step - loss: 0.0207 - acc: 0.9961 - val_loss: 0.6986 - val_acc: 0.8228
Epoch 11/20
6592/6680 [============================>.] - ETA: 0s - loss: 0.0156 - acc: 0.9977Epoch 00011: val_loss did not improve
6680/6680 [==============================] - 1s 154us/step - loss: 0.0160 - acc: 0.9973 - val_loss: 0.7039 - val_acc: 0.8204
Epoch 12/20
6592/6680 [============================>.] - ETA: 0s - loss: 0.0133 - acc: 0.9971Epoch 00012: val_loss did not improve
6680/6680 [==============================] - 1s 154us/step - loss: 0.0138 - acc: 0.9970 - val_loss: 0.7102 - val_acc: 0.8263
Epoch 13/20
6496/6680 [============================>.] - ETA: 0s - loss: 0.0104 - acc: 0.9985Epoch 00013: val_loss did not improve
6680/6680 [==============================] - 1s 154us/step - loss: 0.0103 - acc: 0.9984 - val_loss: 0.7723 - val_acc: 0.8192
Epoch 14/20
6528/6680 [============================>.] - ETA: 0s - loss: 0.0086 - acc: 0.9982Epoch 00014: val_loss did not improve
6680/6680 [==============================] - 1s 154us/step - loss: 0.0085 - acc: 0.9982 - val_loss: 0.7992 - val_acc: 0.8228
Epoch 15/20
6528/6680 [============================>.] - ETA: 0s - loss: 0.0075 - acc: 0.9985Epoch 00015: val_loss did not improve
6680/6680 [==============================] - 1s 154us/step - loss: 0.0074 - acc: 0.9985 - val_loss: 0.7935 - val_acc: 0.8204
Epoch 16/20
6528/6680 [============================>.] - ETA: 0s - loss: 0.0065 - acc: 0.9988Epoch 00016: val_loss did not improve
6680/6680 [==============================] - 1s 155us/step - loss: 0.0064 - acc: 0.9988 - val_loss: 0.7664 - val_acc: 0.8323
Epoch 17/20
6592/6680 [============================>.] - ETA: 0s - loss: 0.0062 - acc: 0.9988Epoch 00017: val_loss did not improve
6680/6680 [==============================] - 1s 155us/step - loss: 0.0061 - acc: 0.9988 - val_loss: 0.8342 - val_acc: 0.8263
Epoch 18/20
6528/6680 [============================>.] - ETA: 0s - loss: 0.0051 - acc: 0.9986Epoch 00018: val_loss did not improve
6680/6680 [==============================] - 1s 155us/step - loss: 0.0051 - acc: 0.9987 - val_loss: 0.8190 - val_acc: 0.8228
Epoch 19/20
6432/6680 [===========================>..] - ETA: 0s - loss: 0.0056 - acc: 0.9988Epoch 00019: val_loss did not improve
6680/6680 [==============================] - 1s 157us/step - loss: 0.0054 - acc: 0.9988 - val_loss: 0.8418 - val_acc: 0.8240
Epoch 20/20
6496/6680 [============================>.] - ETA: 0s - loss: 0.0053 - acc: 0.9985Epoch 00020: val_loss did not improve
6680/6680 [==============================] - 1s 155us/step - loss: 0.0051 - acc: 0.9985 - val_loss: 0.8616 - val_acc: 0.8359
Out[37]:
<keras.callbacks.History at 0x7f133e560048>
In [38]:
### TODO: 加载具有最佳验证loss的模型权重
Resnet50_model.load_weights('DogResnet50.weights.best.hdf5')

【练习】测试模型

问题 8:

在狗图像的测试数据集上试用你的模型。确保测试准确率大于60%。

In [39]:
### TODO: 在测试集上计算分类准确率
# get index of predicted dog breed for each image in test set
Resnet50_predictions = [np.argmax(Resnet50_model.predict(np.expand_dims(feature, axis=0))) 
                         for feature in test_Resnet50]

# report test accuracy
test_accuracy = 100*np.sum(np.array(Resnet50_predictions)==
                           np.argmax(test_targets, axis=1))/len(Resnet50_predictions)
print('\nTest accuracy: %.4f%%' % test_accuracy)
Test accuracy: 81.2201%

【练习】使用模型测试狗的品种

实现一个函数,它的输入为图像路径,功能为预测对应图像的类别,输出为你模型预测出的狗类别(Affenpinscher, Afghan_hound 等)。

与步骤5中的模拟函数类似,你的函数应当包含如下三个步骤:

  1. 根据选定的模型载入图像特征(bottleneck features)
  2. 将图像特征输输入到你的模型中,并返回预测向量。注意,在该向量上使用 argmax 函数可以返回狗种类的序号。
  3. 使用在步骤0中定义的 dog_names 数组来返回对应的狗种类名称。

提取图像特征过程中使用到的函数可以在 extract_bottleneck_features.py 中找到。同时,他们应已在之前的代码块中被导入。根据你选定的 CNN 网络,你可以使用 extract_{network} 函数来获得对应的图像特征,其中 {network} 代表 VGG19, Resnet50, InceptionV3, 或 Xception 中的一个。


问题 9:

In [40]:
### TODO: 写一个函数,该函数将图像的路径作为输入
### 然后返回此模型所预测的狗的品种

def Resnet50_dogdetection(img_path):
    # 根据选定的模型载入图像特徵
    bottleneck_features = extract_Resnet50(path_to_tensor(img_path))
#     print(bottleneck_features)
    # 将图像特徵输入到模型
    predicted_vector = Resnet50_model.predict(bottleneck_features)
#     print(predicted_vector)
    # 返回对应的狗种类名称
    return dog_names[np.argmax(predicted_vector)].split(".")[1]
In [41]:
print(Resnet50_dogdetection(human_files[2]))
Downloading data from https://github.com/fchollet/deep-learning-models/releases/download/v0.2/resnet50_weights_tf_dim_ordering_tf_kernels_notop.h5
94658560/94653016 [==============================] - 2s 0us/step
French_bulldog

步骤 6: 完成你的算法

实现一个算法,它的输入为图像的路径,它能够区分图像是否包含一个人、狗或两者都不包含,然后:

  • 如果从图像中检测到一只,返回被预测的品种。
  • 如果从图像中检测到,返回最相像的狗品种。
  • 如果两者都不能在图像中检测到,输出错误提示。

我们非常欢迎你来自己编写检测图像中人类与狗的函数,你可以随意地使用上方完成的 face_detectordog_detector 函数。你需要在步骤5使用你的CNN来预测狗品种。

下面提供了算法的示例输出,但你可以自由地设计自己的模型!

Sample Human Output

问题 10:

在下方代码块中完成你的代码。


In [43]:
### TODO: 设计你的算法
### 自由地使用所需的代码单元数吧

# 如果从图像中检测到一只狗,返回被预测的品种。
# 如果从图像中检测到人,返回最相像的狗品种。
# 如果两者都不能在图像中检测到,输出错误提示

from PIL import Image        

def dog_app(img_path):
    display(Image.open(img_path))
    if dog_detector(img_path):
        print('Hi, dog! I guess you are a {}!'.format(Resnet50_dogdetection(img_path)))
    elif face_detector(img_path):
        print('Hello, human! You look like a {}!'.format(Resnet50_dogdetection(img_path)))
    else:
        print("Error!")

步骤 7: 测试你的算法

在这个部分中,你将尝试一下你的新算法!算法认为看起来像什么类型的狗?如果你有一只狗,它可以准确地预测你的狗的品种吗?如果你有一只猫,它会将你的猫误判为一只狗吗?

上传方式:点击左上角的Jupyter回到上级菜单,你可以看到Jupyter Notebook的右上方会有Upload按钮。

问题 11:

在下方编写代码,用至少6张现实中的图片来测试你的算法。你可以使用任意照片,不过请至少使用两张人类图片(要征得当事人同意哦)和两张狗的图片。 同时请回答如下问题:

  1. 输出结果比你预想的要好吗 :) ?或者更糟 :( ?
  2. 提出至少三点改进你的模型的想法。
In [45]:
human_files[2]
Out[45]:
'/data/lfw/Carlos_Menem/Carlos_Menem_0013.jpg'
In [66]:
user_test = glob("user-image/*")
user_test
Out[66]:
['user-image/dog1.jpeg',
 'user-image/dog3.jpeg',
 'user-image/xjj3.jpg',
 'user-image/xgg1.jpeg',
 'user-image/dog4.jpeg',
 'user-image/xjj1.jpeg',
 'user-image/dog2.jpeg',
 'user-image/xgg3.jpeg',
 'user-image/xjj2.jpeg',
 'user-image/xgg2.jpeg',
 'user-image/dog5.jpg']
In [67]:
## TODO: 在你的电脑上,在步骤6中,至少在6张图片上运行你的算法。
## 自由地使用所需的代码单元数吧
[dog_app(i) for i in user_test]
Hi, dog! I guess you are a Pembroke_welsh_corgi!
Hi, dog! I guess you are a Alaskan_malamute!
Error!
Hello, human! You look like a Maltese!
Hi, dog! I guess you are a Bulldog!
Hello, human! You look like a Silky_terrier!
Hi, dog! I guess you are a Labrador_retriever!
Error!
Hello, human! You look like a Maltese!
Hello, human! You look like a Maltese!
Hi, dog! I guess you are a Pomeranian!
Out[67]:
[None, None, None, None, None, None, None, None, None, None, None]

回答11:

  1. 输出结果比我预想的好,可以准确的识别出图片中的狗狗,但是有些小姐姐/小哥哥被face_detector识别为人物。
  2. 对当前模型改进的想法:
    • 目前模型使用了Resnet50作为迁移学习的原始模型,可以尝试使用其他的模型架构改进目前的模型,例如:VGG19、InceptionV3 或 Xception等。
    • 导入交叉验证:按某种方式,多次进行训练集/测试集切分,最终取加权平均值。(参考 维基百科))
    • 可以对训练集进行「数据增强」,来优化模型的表现。
    • 在Resnet50架构之后,目前加上的是「全局平均池化层」,可以尝试使用不同的池化层、不同的池化层设置,改进模型的准确率。
    • 多目标监测:可以倒入一些先进的算法,例如:RCNN、Fast-RCNN等,识别图片中的多个目标。

注意: 当你写完了所有的代码,并且回答了所有的问题。你就可以把你的 iPython Notebook 导出成 HTML 文件。你可以在菜单栏,这样导出File -> Download as -> HTML (.html)把这个 HTML 和这个 iPython notebook 一起做为你的作业提交。

In [ ]: